Angular 應用程式主要是利用元件所組合而成,因此開發過程常需要處理元件之間的互動。例如在案例中,對於 AppComponent (父元件) 使用 TaskComponent (子元件) 這種上下層的元件關係,Angular 提供了 @Input()
與 @Output()
兩個裝飾器來處理兩者的互動;另外,Angular 也提供了雙向繫結 (Two-way Binding) 的語法糖,讓元件類別與頁面之間共享資料。這一篇將原在 TaskComponent 內的待辦事項資料移至 AppComponent,來實作兩個元件之間的互動。
@Input()
裝飾器來接收待辦事項資料@Input()
裝飾器用來定義元件屬性是可從父元件接收值,而在父元件則可以利用屬性繫結 (Property Binding) 來傳入資料。
首先,先將原本在 task.component.ts 中的待辦事項屬性移至 app.component.ts 中。
import { Task } from "./model/task";
export class AppComponent implements OnInit {
task: Task;
ngOnInit(): void {
this.task = new Task("頁面需要顯示待辦事項主旨");
}
}
接著,在 task.component.ts 定義主旨與狀態的輸入屬性。
export class TaskComponent implements OnInit {
@Input() subject: string;
@Input() state: TaskState;
constructor() {}
ngOnInit(): void {}
}
最後,在 app.component.html 利用屬性繫結 (Property Bidning) 將待辦事項主旨與狀態傳入 TaskComponent 內。
<app-task [subject]="task.subject" [state]="task.state"></app-task>
是否使用屬性繫結傳入資料,會依需求而定。如果傳入的資料是不會變更的基礎型別資料,那可以直接將值設定至屬性,如
<app-task subject="頁面需要顯示待辦事項主旨" state="TaskState.None"></app-task>
,而不需使用屬性繫結。
setter
實作待辦事項狀態文字原本利用內嵌繫結 (Interpolation) 指定 getStateDesc()
方法來實作狀態文字的顯示;由於已將狀態由外部傳入,因此也可以在 task.component.ts 利用 setter 來監控傳入的狀態值,並判斷與記錄狀態文字。
透過 getter 與 setter 存取屬性值,可以在使用屬性值時,有更高的靈活度。
export class TaskComponent implements OnInit {
@Input() subject: string;
private _state: TaskState;
@Input()
set state(state: TaskState) {
this._state = state;
this.stateDesc = this.getStateDesc();
}
get state(): TaskState {
return this._state;
}
stateDesc: string;
}
針對私有屬性名稱前加入底線 (_),是一種變數命名的風格。但在 Angular 專案的 TSLint 規則定義中, 預設不允許此命名方式,而會出現警告訊息,因此需要修改根目錄下的 tslint.json 檔案,在
variable-name.options
屬性內加入allow-leading-underscore
值。詳細內容可見 TSLint 規則說明。
最後,將 task.component.html 檔案中原來繫結 getStateDesc()
方法,變更為 stateDesc
屬性即可。
<div class="card">
<div class="content">
<span
[class.doing]="state === TaskState.Doing"
[class.finish]="state === TaskState.Finish"
>
{{ stateDesc }}
</span>
</div>
</div>
為了測試上述程式是否正確,在 app.component.ts 中加入一陣列 (Array),其包含了三種狀態的待辦事項,並透過按鈕來決定要繫結的對象。
export class AppComponent implements OnInit {
tasks: Task[];
selectedTask: Task;
ngOnInit(): void {
this.tasks = [
new Task("頁面需要顯示待辦事項主旨"),
new Task("可以設定待辦事項的狀態", TaskState.Doing),
new Task("當待辦事項狀態為已完的事項無法編輯事項", TaskState.Finish),
];
this.onSelectTask(0);
}
onSelectTask(index: number): void {
this.selectedTask = this.tasks[index];
}
}
<div>
<button type="button" (click)="onSelectTask(0)">未完成事項</button>
<button type="button" (click)="onSelectTask(1)">工作中事項</button>
<button type="button" (click)="onSelectTask(2)">已完成事項</button>
</div>
<app-task
[subject]="selectedTask.subject"
[state]="selectedTask.state"
></app-task>
另外,也在 app.component.css 檔案加入樣式設定,讓頁面有更明顯的呈現。
div {
padding: 20px 10px;
}
div button {
margin-right: 5px;
}
ngOnChanges()
實作待辦事項狀態文字當一個元件需要監控多個傳入屬性時,利用 setter
方法會需要較多的處理,如每個屬性都需要一個私有屬性記錄。在 Angular 應用程式中,當傳入參數的值有變更時,會觸發 Angular 生命週期鉤子 (Lifecycle Hook) 中的 ngOnChanges()
方法,並傳入目前與前一次的屬性值。因此這個需求可以改在 task.component.ts 檔案中實作 OnChanges
介面,並在 ngOnChanges()
方法取得狀態文字。
export class TaskComponent implements OnInit, OnChanges {
@Input() subject: string;
@Input() state: TaskState;
stateDesc: string;
ngOnInit(): void {}
ngOnChanges(): void {
this.stateDesc = this.getStateDesc();
}
}
Output()
裝飾器實作待辦事項狀態變更由於目前已將待辦事項資料移至 AppComponent,使得在點選 TaskComponent 的狀態變更按鈕時,需要通知 AppComponent 進行變更。透過 Output()
裝飾器可以在子元件內定義一個 EventEmitter
泛型型別的輸出屬性,且利用此屬性的 emit()
方法來將值傳遞資料給父元件。
export class TaskComponent implements OnInit, OnChanges {
@Input() subject: string;
@Input() state: TaskState;
@Output() stateChange = new EventEmitter<TaskState>();
onSetTaskState(state: TaskState): void {
this.stateChange.emit(state);
}
}
在 task.component.ts 中加入 stateChange
的輸出屬性,並在設定事項狀態的方法中,利用 emit()
方法將狀態傳遞出去。接著,在 app.component.html 透過事件繫結監控 stateChange
,進而變更待辦事項的狀態。
<app-task
[subject]="selectedTask.subject"
[state]="selectedTask.state"
(stateChange)="onStateChange($event)"
></app-task>
export class AppComponent implements OnInit {
onStateChnage(state: TaskState): void {
this.selectedTask.state = state;
}
}
Input()
與Output()
兩個裝飾器可以傳入字別,來變更父元件繫結所使用的名稱。不過,依 Angular 風格指南 ,在元件的定義上並不建議這麼做。
上面程式利用了屬性繫結 (Property Binding) 設定 TaskComponent 中狀態屬性,並利用事件繫結 (Event Binding) 來監控與變更狀態屬性。而針對此種針對資料的設定與監控變更的需求,Angular 提供了一個名為雙向繫結 (Two-way Binding)的語法糖來簡化程式,其語法為 [()]
(可用盒子裡的香蕉來記憶),是將屬性繫結與事件繫結語法合併而成。
因此,可以在 app.component.html 利用雙向繫結語法綁定 selectedTask.state
屬性,來實作變更待辦事項變更;此時,Angular 會利用 TaskComponent 內的 state
輸入屬性與 stateChange
輸出屬性(此名稱須以 Change
為結尾)來設定與變更待辦事項狀態的資料。
<app-task
[subject]="selectedTask.subject"
[(state)]="selectedTask.state"
></app-task>
這篇利用了 Input()
與 Output()
兩個裝飾器,來串連 AppComponent 與 TaskComponent 兩個元件,並使用雙向繫結 (Two-way Binding) 來實作狀態變更的需求,實作的程式碼可至 GitHub 參考。